/*
 * XLoginDialog.java, created on Oct 20, 2005
 * 
 * Copyright (c) 2005 - 2007
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.xfc.dialog;

import java.awt.Color;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import org.xfc.XApp;
import org.xfc.components.XMultilineLabel;
import org.xfc.components.XThrobber;

import com.jgoodies.forms.layout.FormLayout;

/**
 * 
 * 
 * @author Devon Carew
 */
public class XLoginDialog
	extends XDialog
{
	private static final char BULLET_CHAR = '\u2022';
	
	private boolean 		okToCallShow;
	private boolean			userAuthenticated;
	
	private XThrobber 		throbber;
	private JLabel			loginImageLabel;
	private XMultilineLabel	loginTextLabel;
	
	private JTextField		usernameField;
	private JPasswordField	passwordField;
	
	private Authenticator	authenticator;
	
	private Action			loginAction = new AbstractAction("Login") {
		public void actionPerformed(ActionEvent event) {
			startAuthentication();
		}
	};
	
	public XLoginDialog()
	{
		this(XApp.getApp());
	}
	
	public XLoginDialog(XApp app)
	{
		super(app);
		
		setTitle(app.getAppName() + " Login");
		
		initUI();
		
		setModal(true);
		
		String lastUsername = loadLastUsername();
		
		if (lastUsername != null && lastUsername.length() > 0)
			usernameField.setText(lastUsername);
		
		construct();
	}
	
	private void initUI()
	{
		loginTextLabel = new XMultilineLabel();
		loginTextLabel.setPreferredWidth(300);
		
		throbber = new XThrobber(true);
		
		JPanel topPanel = new JPanel(new FormLayout(
			"pref:grow 2dlu pref",
			"pref"
		));
		
		topPanel.add(loginTextLabel, 	"1, 1, f, c");
		topPanel.add(throbber, 			"3, 1, r, t");
		
		loginImageLabel = new JLabel();
		loginImageLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
		loginImageLabel.setVisible(false);
		
		addAction(loginAction, XDialog.OK_POSITION, true);
		
		setCancelAction(getFrameCloseAction());
		
		usernameField = new JTextField();
		usernameField.setColumns(12);
		usernameField.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				passwordField.requestFocus();
			}
		});
		
		passwordField = new JPasswordField() {
			public void addNotify() {
				super.addNotify();
				
				if (getUsername().length() > 0)
					passwordField.requestFocusInWindow();
			}
		};
		passwordField.setColumns(12);
		passwordField.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				startAuthentication();
			}
		});
		if (passwordField.getFont().canDisplay(BULLET_CHAR))
			passwordField.setEchoChar(BULLET_CHAR);
		
		JLabel userNameLabel = new JLabel("User name:");
		JLabel passwordLabel = new JLabel("Password:");
		
		JPanel textFieldPanel = new JPanel(new FormLayout(
			"pref 8dlu pref",
			"pref 4dlu pref"
		));
		
		textFieldPanel.add(userNameLabel, "1, 1, r, c");
		textFieldPanel.add(usernameField, "3, 1, f, c");
		textFieldPanel.add(passwordLabel, "1, 3, r, c");
		textFieldPanel.add(passwordField, "3, 3, f, c");
		
		JPanel content = new JPanel(new FormLayout(
			"pref 20dlu pref:grow",
			"pref 6dlu pref:grow"
		));
			
		content.add(topPanel,			"1, 1, 3, 1, f, c");
		content.add(loginImageLabel,	"1, 3");
		content.add(textFieldPanel,		"3, 3, c, c");
		
		setContents(content);
	}
	
	public Action getFrameCloseAction()
	{
		return new AbstractAction() {
			public void actionPerformed(ActionEvent event) {
				loginCancelled();
			}
		};
	}
	
	/**
	 * The method shows the dialog and attempts to authenticate the user with the given Authenticator
	 * callback. This method returns true if the user was sucessfully authenticated, false otherwise.
	 * This method should be called on the event thread. It will block until the dialog is closed.
	 * 
	 * @param authenticator
	 *            a callback to allow the dialog to authenticate the user with a given username and password
	 * 
	 * @return Returns true if the user was sucessfully authenticated, false otherwise.
	 */
	public boolean authenticate(Authenticator authenticator)
	{
		if (!SwingUtilities.isEventDispatchThread())
			throw new IllegalStateException("authenticate() should be called on the event thread");
		
		this.authenticator = authenticator;
		
		okToCallShow = true;
		
		userAuthenticated = false;
		
		try
		{
			show();
		}
		finally
		{
			okToCallShow = false;
		}
		
		this.authenticator = null;
		
		boolean result = userAuthenticated;
		
		userAuthenticated = false;
		
		return result;
	}
	
	private void startAuthentication()
	{
		loginAction.setEnabled(false);
		throbber.startThrobber();
		setBusy(true);
		
		new Thread(new Runnable() {
			public void run() {
				try
				{
					userAuthenticated = authenticator.authenticate(
						usernameField.getText(), new String(passwordField.getPassword()));
				}
				catch (Throwable t)
				{
					t.printStackTrace();
				}
				
				throbber.stopThrobber();
				loginAction.setEnabled(true);
				setBusy(false);
				
				if (userAuthenticated)
					loginSuccessful();
				else
					loginFailed();
			}
		}).start();
	}
	
	private void loginSuccessful()
	{
		userAuthenticated = true;
		
		saveUsername(getUsername());
		
		hide();
	}
	
	private void loginCancelled()
	{
		userAuthenticated = false;
		
		hide();
	}
	
	private void loginFailed()
	{
		userAuthenticated = false;
		
		Toolkit.getDefaultToolkit().beep();
	}
	
	private String loadLastUsername()
	{
		Preferences prefs = getApp().getPreferences(getClass());
		
		return prefs.get("username", null);
	}
	
	private void saveUsername(String username)
	{
		Preferences prefs = getApp().getPreferences(getClass());
		
		prefs.put("username", username);
	}
	
	public void show()
	{
		if (!okToCallShow)
			throw new IllegalStateException("The only valid way of showing the XLoginDialog is through the authenticate() method");
		
		super.show();
	}
	
	public String getUsername()
	{
		return usernameField.getText();
	}
	
	public void setLoginImage(ImageIcon icon)
	{
		loginImageLabel.setVisible(icon != null);
		loginImageLabel.setIcon(icon);
	}
	
	public void setLoginText(String text)
	{
		loginTextLabel.setText(text);
	}
	
	/**
	 * A callback interface to provide the XLoginDialog with a way to authenticate users.
	 */
	public static interface Authenticator
	{
		
		/**
		 * A callback interface to provide the XLoginDialog with a way to authenticate users.
		 * This method will be called on a background, non-event thread. The method should return
		 * true if the user was sucessfully authenticated, false otherwise. 
		 * 
		 * @param username
		 * @param password
		 * 
		 * @return Returns true if the user was sucessfully authenticated, false otherwise.
		 */
		public boolean authenticate(String username, String password);
		
	}

}
